package com.serial.comm.core.utils;

import com.serial.comm.core.constants.SerialConst;
import com.serial.comm.core.exception.*;
import com.serial.comm.core.exception.enums.ResponseCodeEnum;
import com.serial.comm.core.telegram.utils.ITA2Util;
import com.serial.comm.modular.serialPort.listener.SerialReadListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import purejavacomm.*;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharsetDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.TooManyListenersException;


/**
 * 操作串口的工具类
 */
@Component
public class SerialUtils {

    public static Logger log = LoggerFactory.getLogger(SerialUtils.class);

    public static CharsetDecoder ita2Decoder = new ITA2Util().newDecoder();

    private static SerialUtils serialUtils;

    private static StringBuilder frameBuffer = new StringBuilder();

    /**
     * 串口对象
     */
    private static SerialPort serialPort;

    static {
        //在该类被ClassLoader加载时就初始化一个SerialTool对象
        serialUtils = new SerialUtils();
    }

    @PostConstruct
    public void init() {
        serialUtils = this;
    }


    //单例
    private SerialUtils() {
    }

    /**
     * 获取单例
     *
     * @return serialTool
     */
    public static SerialUtils getSerialUtils() {
        if (serialUtils == null) {
            serialUtils = new SerialUtils();
        }
        return serialUtils;
    }


    /**
     * 发现可用串口列表
     *
     * @return 列表名称集合
     */
    public static final ArrayList<String> findPort() {
        //枚举集合
        Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers();
        ArrayList<String> portNameList = new ArrayList<>();
        while (portList.hasMoreElements()) {
            String portName = portList.nextElement().getName();
            portNameList.add(portName);
        }
        return portNameList;
    }

    /**
     * 打开串口
     *
     * @param portName 端口名称
     * @param baudrate 波特率
     * @param dtBit    数据位
     * @param stpBit   停止位
     * @return 串口对象
     * @throws SerialPortParameterFailure 设置串口参数失败
     * @throws NotASerialPort             端口指向设备不是串口类型
     * @throws NoSuchPort                 没有该端口对应的串口设备
     * @throws PortInUse                  端口已被占用
     */
    public static final SerialPort openPort(String portName, int baudrate, int dtBit, int stpBit) throws SerialPortParameterFailure, NotASerialPort, NoSuchPort, PortInUse {

        try {

            //通过端口名识别端口
            CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);

            //打开端口，并给端口名字和一个timeout（打开操作的超时时间 2s）
            CommPort commPort = portIdentifier.open(portName, SerialConst.BusinessMessage.TWO_SECOND_DELAY);

            //判断是不是串口
            if (commPort instanceof SerialPort) {

                serialPort = (SerialPort) commPort;

                try {
                    //设置一下串口的波特率等参数
                    serialPort.setSerialPortParams(baudrate, dtBit, stpBit, SerialPort.PARITY_NONE);
                    //在该串口对象上添加监听器
                    SerialUtils.addListener(serialPort, SerialReadListener.initSerialPort(serialPort));
                    serialPort.notifyOnOutputEmpty(true);
                    serialPort.notifyOnCTS(true);


                } catch (UnsupportedCommOperationException e) {
                    throw new SerialPortParameterFailure(ResponseCodeEnum.SERIAL_PORT_PARAMETER_FAILURE);
                }

                log.info(">>>>>>打开串口 " + portName + " 成功 !");
                return serialPort;

            } else {
                //不是串口 抛异常
                throw new NotASerialPort(ResponseCodeEnum.NOT_A_SERIAL_PORT);
            }
        } catch (NoSuchPortException e1) {
            throw new NoSuchPort(ResponseCodeEnum.NO_SUCH_PORT);
        } catch (PortInUseException e2) {
            throw new PortInUse(ResponseCodeEnum.PORT_IN_USE);
        }
    }

    /**
     * 关闭串口
     */
    public static void closePort() {
        if (serialPort != null) {
            serialPort.close();
            serialPort = null;
            log.info(SerialConst.SysMessage.PORT_CLOSED);
        } else {
            throw new NoSuchPort(ResponseCodeEnum.NO_SUCH_PORT);
        }
    }

    /**
     * 往串口发送数据
     *
     * @param order 待发送数据
     * @throws SendDataToSerialPortFailure        向串口发送数据失败
     * @throws SerialPortOutputStreamCloseFailure 关闭串口对象的输出流出错
     */
    public static void sendToPort(byte[] order) throws SendDataToSerialPortFailure, SerialPortOutputStreamCloseFailure {
        if (serialPort == null) {
            throw new NoSuchPort(ResponseCodeEnum.NO_SUCH_PORT);
        }

        OutputStream out = null;
        try {
            Thread.sleep(SerialConst.BusinessMessage.TWO_SECOND_DELAY);//这个延时非常重要
            out = serialPort.getOutputStream();
            out.write(order);
            out.flush();
        } catch (IOException | InterruptedException e) {
            throw new SendDataToSerialPortFailure(ResponseCodeEnum.SEND_DATA_TO_SERIAL_PORT_FAILURE);
        } finally {
            try {
                if (out != null) {
                    out.close();
                    out = null;
                }
            } catch (IOException e) {
                throw new SerialPortOutputStreamCloseFailure(ResponseCodeEnum.SERIAL_PORT_OUTPUTSTREAM_CLOSE_FAILURE);
            }
        }

    }

    /**
     * 从串口读取数据
     *
     * @param serialPort 当前已建立连接的SerialPort对象
     * @return 读取到的数据
     * @throws ReadDataFromSerialPortFailure     从串口读取数据时出错
     * @throws SerialPortInputStreamCloseFailure 关闭串口对象输入流出错
     */
    public static String readFromPort(SerialPort serialPort) throws ReadDataFromSerialPortFailure, SerialPortInputStreamCloseFailure {

        InputStream in = null;
        byte[] bytes;
        String recData = "";
        try {
            in = serialPort.getInputStream();
            Thread.sleep(SerialConst.BusinessMessage.ONE_SECOND_DELAY);//这个延时非常重要,确保在读取串口数据时不出现截断
            int buffLength = in.available();
            while (buffLength != 0) {
                //初始化byte数组为buffer中数据的长度
                bytes = new byte[buffLength];
                Thread.sleep(SerialConst.BusinessMessage.ONE_SECOND_DELAY);//这个延时非常重要,确保在读取串口数据时不出现截断
                in.read(bytes);
                log.info(">>>>>>从串口读取到的元数据读取成字节码后: {}", bytes);
                CharBuffer ra = ita2Decoder.decode(ByteBuffer.wrap(bytes));
                log.info(">>>>>>从串口读取到的元数据: {}",ra);
                buffLength = in.available();
                String temp = ra.toString();
                frameBuffer.append(temp);
            }

            recData = frameBuffer.toString();
            //这种方法清空效率最高  另外两种是 a.新建一个对象旧的由系统回收  b.sb3.setLength( 0 );
            frameBuffer.delete(0, frameBuffer.length());
        } catch (IOException e) {
            throw new ReadDataFromSerialPortFailure(ResponseCodeEnum.READ_DATA_FROM_SERIAL_PORT_FAILURE);
        } catch (InterruptedException e) {
            log.error(new InterruptedException().toString(), new InterruptedException());
        } finally {
            try {
                if (in != null) {
                    in.close();
                    in = null;
                }
            } catch (IOException e) {
                throw new SerialPortInputStreamCloseFailure(ResponseCodeEnum.SERIAL_PORT_INPUTSTREAM_CLOSE_FAILURE);
            }

        }
        return recData;

    }

    /**
     * 添加监听器
     *
     * @param port     串口对象
     * @param listener 串口监听器
     * @throws TooManyListeners 监听类对象过多
     */
    public static void addListener(SerialPort port, SerialPortEventListener listener) throws TooManyListeners {

        try {
            //给串口添加监听器
            port.addEventListener(listener);
            //设置当有数据到达时唤醒监听接收线程
            port.notifyOnDataAvailable(true);
            //设置当通信中断时唤醒中断线程
            port.notifyOnBreakInterrupt(true);

        } catch (TooManyListenersException e) {
            throw new TooManyListeners(ResponseCodeEnum.TOO_MANY_LISTENERS);
        }
    }


}
